﻿namespace net.zxteam.ZXLexer.Runtime.Internal
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using net.zxteam.ZXLexer.AST;
	using net.zxteam.ZXLexer.Runtime.XSD;

	internal class RuntimeLexerImpl : IGrammarVisitor
	{

		private struct NTokenLocation : Location
		{
			private readonly int _start, _end;

			public NTokenLocation(int start, int end)
			{
				this._start = start;
				this._end = end;
			}

			public int PosEnd { get { return _end; } }
			public int PosStart { get { return _start; } }
		}

		public static readonly Dictionary<string, IGrammar> _rules = new Dictionary<string, IGrammar>();

		private LexerSourceTextReader LexerSource
		{
			get { return _lexerSource; }
			set
			{
				if (_lexerSource != null)
				{
				}

				_lexerSource = value;

				if (_lexerSource != null)
				{
				}
			}
		}
		private LexerSourceTextReader _lexerSource;

		public RuntimeLexerImpl(LexerSourceTextReader lexerSource)
		{
			if (lexerSource == null) throw new ArgumentNullException();

			this.LexerSource = lexerSource;
		}

		private Token CreateASTNode(IGrammarRule rule, string text)
		{
			var location = new NTokenLocation(_lexerSource.StateInitPosition + 1, _lexerSource.Position);

			var ast = new Token(rule.GetType().Name, location, rule.name, text);
			return ast;
		}
		private Token CreateASTNode(IGrammarRule rule, params Token[] children)
		{
			var location = new NTokenLocation(_lexerSource.StateInitPosition + 1, _lexerSource.Position);

			return new Token(rule.GetType().Name, location, rule.name, children);
		}
		//private static Token.OutputType TraslateOutputType(renderModeType? mode)
		//{
		//	if (!mode.HasValue) return Token.OutputType.TOKEN;

		//	switch (mode)
		//	{
		//		case renderModeType.innerContent: return Token.OutputType.INNER_CONTENT;
		//		case renderModeType.innerSkip: return Token.OutputType.INNER_SKIP;
		//		case renderModeType.innerTokens: return Token.OutputType.INNER_TOKENS;
		//		case renderModeType.content: return Token.OutputType.CONTENT;
		//		case renderModeType.skip: return Token.OutputType.SKIP;
		//		case renderModeType.token: return Token.OutputType.TOKEN;
		//		default: throw new NotSupportedException("Not supported value: " + mode);
		//	}
		//}



		public Token VisitGrammarRule(grammar grammarRule)
		{
			lock (this)
			{
				try
				{
                    List<IGrammar> refRules = new List<IGrammar>();
					foreach (var tokenRule in grammarRule.Items.OfType<IRootGrammarRule>())
					{
						if (tokenRule != null)
						{
                            if (tokenRule is referenceRuleType)
                            {
                                refRules.Add(tokenRule);
                                //((referenceRuleType)tokenRule).rules = _rules;
                            }
                            else
                            {
                                _rules.Add(tokenRule.id, tokenRule);
                            }
						}
					}

                    foreach (var refRule in refRules)
                    {

                    }

					return grammarRule.ast.Accept(this);
				}
				finally
				{
					_rules.Clear();
				}
			}
		}

		public Token VisitAstRule(grammarAst astRule)
		{
			Token child = ((IGrammar)astRule.Item).Accept(this);
			//if (_parsingSource.HasNext) throw new RuntimeParserException(0, _parsingSource.Position);
			return new Token(astRule.GetType().Name, new NTokenLocation(0, child.Location.PosEnd), astRule.name ?? "ast", child);
		}

		public Token VisitCardinalRule(cardinalRuleType cardinalRule)
		{
			using (var scopeParsingSource = _lexerSource.CreateStateScope())
			{
				List<Token> children = new List<Token>();

				var childRule = (IGrammar)cardinalRule.Item;
				                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          while (true)
				{
					// parse while error don't occurs
					try
					{
						Token astNode = childRule.Accept(this);

						if (cardinalRule.occurs == cardinalRuleTypeOccurs.Item2/*("?")*/)
						{
							scopeParsingSource.Commit();
							return CreateASTNode(cardinalRule, astNode);
						}

						children.Add(astNode);
					}
					catch
					{
						if (cardinalRule.occurs == cardinalRuleTypeOccurs.Item2/*("?")*/)
						{
							throw;
						}

						break;
					}
				}

				switch (cardinalRule.occurs)
				{
					case cardinalRuleTypeOccurs.Item/*("*")*/:
						{
							scopeParsingSource.Commit();
							return CreateASTNode(cardinalRule, children.ToArray());
						}
					case cardinalRuleTypeOccurs.Item1/*("+")*/:
						{
							if (children.Count() > 0)
							{
								scopeParsingSource.Commit();
								return CreateASTNode(cardinalRule, children.ToArray());
							}
							else
							{
								throw new RuntimeLexer.RuntimeLexerException(_lexerSource.StateInitPosition, _lexerSource.Position);
							}
						}
					default:
						{
							if (children.Count() == 1)
							{
								scopeParsingSource.Commit();
								return CreateASTNode(cardinalRule, children.ToArray());
							}
							else
							{
								throw new RuntimeLexer.RuntimeLexerException(_lexerSource.StateInitPosition, _lexerSource.Position);
							}
						}

				}
			}
		}

		public Token VisitChoiceRule(choiceRuleType choiceRule)
		{
			using (var scopeParsingSource = _lexerSource.CreateStateScope())
			{
				List<Exception> exs = new List<Exception>();
				foreach (IGrammar child in choiceRule.Items)
				{
					try
					{
						Token attempt = child.Accept(this);

						scopeParsingSource.Commit();
						return CreateASTNode(choiceRule, attempt);
					}
					catch (Exception ex)
					{
						if (!(child is cardinalRuleType && ((cardinalRuleType)child).occurs == cardinalRuleTypeOccurs.Item2))
						{
							exs.Add(ex);
						}
					}
				}

				throw new AggregateException(exs);
			}
		}

		public Token VisitSequenceRule(sequenceRuleType sequenceRule)
		{
			using (var scopeParsingSource = _lexerSource.CreateStateScope())
			{
				List<Token> astChildren = new List<Token>();
				foreach (IGrammar child in sequenceRule.Items)
				{
					try
					{
						Token attempt = child.Accept(this);
						astChildren.Add(attempt);
					}
					catch
					{
						if (!(child is cardinalRuleType && ((cardinalRuleType)child).occurs == cardinalRuleTypeOccurs.Item2))
						{
							throw;
						}
					}
				}

				scopeParsingSource.Commit();
				return CreateASTNode(sequenceRule, astChildren.ToArray());
			}
		}

		public Token VisitRangeRule(rangeRuleType rangeRule)
		{
			using (var stateScope = _lexerSource.CreateStateScope())
			{
				var from = rangeRule.from[0];
				var to = rangeRule.to[0];

				char ch = _lexerSource.ReadChar();
				if (ch >= from && ch <= to)
				{
					stateScope.Commit();
					return CreateASTNode(rangeRule, ch.ToString());
				}

				throw new RuntimeLexer.RuntimeLexerException(_lexerSource.Position, _lexerSource.Position);
			}
		}

        //public Token VisitReferenceRule(referenceRuleType referenceRule)
        //{
        //    using (var scopeParsingSource = _lexerSource.CreateStateScope())
        //    {
        //        string refName = referenceRule.@ref;
        //        if (this._rules.ContainsKey(refName))
        //        {
        //            var realRule = this._rules[refName];
        //            Token child = realRule.Accept(this);
        //            scopeParsingSource.Commit();
        //            return CreateASTNode(referenceRule, child);
        //        }

        //        throw new RuntimeLexer.RuntimeLexerException(_lexerSource.StateInitPosition, _lexerSource.Position);
        //    }
        //}

		public Token VisitCharRule(charRuleType charRule)
		{
			using (var scopeParsingSource = _lexerSource.CreateStateScope())
			{
				char? ruleCh = null;
				if (charRule.value.Length == 1)
					ruleCh = charRule.value[0];
				if (!ruleCh.HasValue && charRule.value.Length == 6)
				{
					// try unicode: \u1234
					int uniChar;
					if (int.TryParse(charRule.value.Substring(2), System.Globalization.NumberStyles.HexNumber, null, out uniChar))
					{
						ruleCh = (char)uniChar;
					}
				}
				if (!ruleCh.HasValue)
					throw new ArgumentException("Bad rule value: " + charRule.value);

				char ch = _lexerSource.ReadChar();
				if (ch == ruleCh)
				{
					scopeParsingSource.Commit();
					return CreateASTNode(charRule, ch.ToString());
				}

				throw new RuntimeLexer.RuntimeLexerException(_lexerSource.StateInitPosition, _lexerSource.Position);
			}
		}

		public Token VisitStringRule(stringRuleType stringRule)
		{
			using (var scopeParsingSource = _lexerSource.CreateStateScope())
			{
				string ruleStr = stringRule.value;
				List<char> chars = new List<char>();
				for (int i = ruleStr.Length; i > 0; --i)
					chars.Add(_lexerSource.ReadChar());

				string parsedStr = new string(chars.ToArray());
				if (ruleStr == parsedStr)
				{
					scopeParsingSource.Commit();
					return CreateASTNode(stringRule, parsedStr);
				}

				throw new RuntimeLexer.RuntimeLexerException(_lexerSource.StateInitPosition, _lexerSource.Position);
			}
		}


		readonly sequenceRuleType _inderectionNewLineRule = new sequenceRuleType()
		{
			Items = new object[] {
				new charRuleType() { value = "\r" },
				new charRuleType() { value = "\n" },
			}
		};
		public Token VisitIndentationRule(indentationRuleType indentationRule)
		{
			using (var stateScope = _lexerSource.CreateStateScope())
			{
				var skip = _inderectionNewLineRule.Accept(this);
				using (var indentScope = _lexerSource.CreateIndentScope())
				{
					Token astNode = ((IGrammar)indentationRule.Item).Accept(this);
					stateScope.Commit();
					return CreateASTNode(indentationRule, astNode);
				}
			}
		}
	}
}
